use std::mem;

use crate::{
    common::{
        closure::Closure, data::Data, lambda::Captured, number::build_number, opcode::Opcode,
        span::Span,
    },
    vm::{slot::Suspend, stack::Stack, trace::Trace},
};

// TODO: algebraic effects
// more than just Trace, Runtime - mechanism for raising effects
// fiber scheduling environment handles FFI, no more holding refs to rust
// functions. TODO: convert Fiber to Fiber

/// A `Fiber` executes bytecode lambda closures.
/// (That's a mouthful - think bytecode + some context).
/// Fiber initialization overhead is tiny,
/// and each Fiber's state is self-contained,
/// so more than one can be spawned if needed.
#[derive(Debug)]
pub struct Fiber {
    pub closure: Closure,
    pub stack: Stack,
    pub ip: usize,
}

unsafe impl Send for Fiber {}

// NOTE: use Opcode::same and Opcode.to_byte() rather than actual bytes
// Don't worry, the compiler *should* get rid of this overhead and just use
// bytes

// this impl contains initialization, helper functions, and the core interpreter
// loop the next impl contains opcode implementations
impl Fiber {
    /// Initialize a new Fiber.
    /// To run the Fiber, a lambda must be passed to it through `run`.
    pub fn init(closure: Closure) -> Fiber {
        let mut fiber = Fiber {
            closure,
            stack: Stack::init(),
            ip: 0,
        };
        fiber.stack.declare(fiber.closure.lambda.decls);
        return fiber;
    }

    /// Advances to the next instruction.
    #[inline]
    fn next(&mut self) {
        self.ip += 1;
    }

    /// Advances IP, returns `Ok`. Used in Bytecode implementations.
    #[inline]
    fn done(&mut self) -> Result<(), Trace> {
        self.next();
        Ok(())
    }

    /// Returns the current instruction as a byte.
    #[inline]
    fn peek_byte(&mut self) -> u8 {
        self.closure.lambda.code[self.ip]
    }

    /// Advances IP and returns the current instruction as a byte.
    #[inline]
    fn next_byte(&mut self) -> u8 {
        self.next();
        self.peek_byte()
    }

    /// Returns whether the program has terminated
    #[inline]
    fn is_terminated(&mut self) -> bool {
        self.ip >= self.closure.lambda.code.len()
    }

    /// Builds the next number in the bytecode stream.
    /// See `utils::number` for more.
    #[inline]
    fn next_number(&mut self) -> usize {
        self.next();
        let remaining = &self.closure.lambda.code[self.ip..];
        let (index, eaten) = build_number(remaining);
        self.ip += eaten - 1; // ip left on next op
        return index;
    }

    #[inline]
    fn current_span(&self) -> Span {
        self.closure.lambda.index_span(self.ip)
    }

    // core interpreter loop

    /// Dissasembles and interprets a single (potentially fallible) bytecode op.
    /// The op definitions follow in the next `impl` block.
    /// To see what each op does, check `common::opcode::Opcode`.
    fn step(&mut self) -> Result<(), Trace> {
        let opcode = Opcode::from_byte(self.peek_byte());

        match opcode {
            Opcode::Con => self.con(),
            Opcode::NotInit => self.not_init(),
            Opcode::Del => self.del(),
            Opcode::FFICall => self.ffi_call(),
            Opcode::Copy => self.copy_val(),
            Opcode::Capture => self.capture(),
            Opcode::Save => self.save(),
            Opcode::SaveCap => self.save_cap(),
            Opcode::Load => self.load(),
            Opcode::LoadCap => self.load_cap(),
            Opcode::Call => self.call(),
            Opcode::Return => self.return_val(),
            Opcode::Closure => self.closure(),
            Opcode::Print => self.print(),
            Opcode::Label => self.label(),
            Opcode::Tuple => self.tuple(),
            Opcode::UnData => self.un_data(),
            Opcode::UnLabel => self.un_label(),
            Opcode::UnTuple => self.un_tuple(),
            Opcode::Noop => self.done(),
            _ => panic!("Opcode Not Implemented"),
        }
    }

    fn unwind(&mut self) {
        // restore suspended callee
        let suspend = self.stack.pop_frame(); // remove the frame
        self.ip = suspend.ip;
        self.closure = suspend.closure;

        // indicate failure
        self.stack.push_not_init();
    }

    /// Suspends the current lambda and runs a new one on the Fiber.
    /// Runs until either success, in which it restores the state of the
    /// previous lambda, Or failure, in which it returns the runtime error.
    /// In the future, fibers will allow for error handling -
    /// right now, error in Passerine are practically panics.
    pub fn run(&mut self) -> Result<(), Trace> {
        // println!("Starting\n{}", self.closure.lambda);
        let mut result = Ok(());

        while !self.is_terminated() {
            // println!("before: {:#?}", self.stack.stack);
            // println!("executing: {:?}", Opcode::from_byte(self.peek_byte()));
            result = self.step();
            if result.is_err() {
                break;
            }
            // println!("---");
        }
        // println!("after: {:?}", self.stack.stack);
        // println!("---");

        if let Err(mut trace) = result {
            while self.stack.unwind_frame() {
                self.unwind();
                self.ip -= 1;
                trace.add_context(self.current_span());
            }

            result = Err(trace);
        };

        return result;
    }

    /// Load a constant and push it onto the stack.
    #[inline]
    fn con(&mut self) -> Result<(), Trace> {
        // get the constant index
        let index = self.next_number();

        self.stack
            .push_data(self.closure.lambda.constants[index].clone());
        self.done()
    }

    #[inline]
    fn not_init(&mut self) -> Result<(), Trace> {
        self.stack.push_not_init();
        self.done()
    }

    /// Moves the top value on the stack to the heap,
    /// replacing it with a reference to the heapified value.
    #[inline]
    fn capture(&mut self) -> Result<(), Trace> {
        let index = self.next_number();
        self.stack.local_ref(index); // move value to the heap
        self.done()
    }

    /// Save the topmost value on the stack into a variable.
    #[inline]
    fn save(&mut self) -> Result<(), Trace> {
        let index = self.next_number();
        self.stack.set_local(index);
        self.done()
    }

    /// Save the topmost value on the stack into a captured variable.
    #[inline]
    fn save_cap(&mut self) -> Result<(), Trace> {
        let index = self.next_number();
        let data = self.stack.pop_data();
        mem::drop(self.closure.captures[index].replace(data));
        self.done()
    }

    /// Push a copy of a variable's value onto the stack.
    #[inline]
    fn load(&mut self) -> Result<(), Trace> {
        let index = self.next_number();
        let mut data = self.stack.local_data(index);
        self.stack.push_data(data);
        self.done()
    }

    /// Load a captured variable from the current closure.
    #[inline]
    fn load_cap(&mut self) -> Result<(), Trace> {
        let index = self.next_number();
        let data = self.closure.captures[index].borrow().to_owned();
        self.stack.push_data(data);
        self.done()
    }

    /// Delete the top item of the stack.
    #[inline]
    fn del(&mut self) -> Result<(), Trace> {
        mem::drop(self.stack.pop_data());
        self.done()
    }

    /// Copy the top data of the stack, i.e.
    /// `[F, D]` becomes `[F, D, D]`.
    #[inline]
    fn copy_val(&mut self) -> Result<(), Trace> {
        let data = self.stack.pop_data();
        self.stack.push_data(data.clone());
        self.stack.push_data(data);
        self.done()
    }

    #[inline]
    fn print(&mut self) -> Result<(), Trace> {
        let data = self.stack.pop_data();
        println!("{}", data);
        self.stack.push_data(data);
        self.done()
    }

    #[inline]
    fn label(&mut self) -> Result<(), Trace> {
        let kind = match self.stack.pop_data() {
            Data::Kind(n) => n,
            _ => unreachable!(),
        };
        let data = self.stack.pop_data();
        self.stack.push_data(Data::Label(kind, Box::new(data)));
        self.done()
    }

    #[inline]
    fn tuple(&mut self) -> Result<(), Trace> {
        let index = self.next_number();
        let mut items = vec![];
        for _ in 0..index {
            items.push(self.stack.pop_data())
        }

        items.reverse();
        self.stack.push_data(Data::Tuple(items));
        self.done()
    }

    fn un_data(&mut self) -> Result<(), Trace> {
        let expected = self.stack.pop_data();
        let data = self.stack.pop_data();

        if data != expected {
            return Err(Trace::error(
                "Pattern Matching",
                &format!(
                    "The data '{}' does not match the expected data '{}'",
                    data, expected
                ),
                vec![self.current_span()],
            ));
        }

        self.done()
    }

    fn un_label(&mut self) -> Result<(), Trace> {
        let kind = match self.stack.pop_data() {
            Data::Kind(n) => n,
            _ => unreachable!(),
        };

        let d = match self.stack.pop_data() {
            Data::Label(n, d) if n == kind => d,
            other => {
                return Err(Trace::error(
                    "Pattern Matching",
                    &format!("The data '{}' does not match the Label '{}'", other, kind),
                    vec![self.current_span()],
                ))
            }
        };

        self.stack.push_data(*d);
        self.done()
    }

    fn un_tuple(&mut self) -> Result<(), Trace> {
        let index = self.next_number();
        let t = match self.stack.pop_data() {
            Data::Tuple(t) => t,
            other => {
                return Err(Trace::error(
                    "Pattern Matching",
                    &format!("The data '{}' is not a tuple", other),
                    vec![self.current_span()],
                ))
            }
        };

        let length = t.len();
        if index >= length {
            return Err(Trace::error(
                "Indexing",
                &format!(
                    "The tuple '{}' is of length {}, so the index {} is out-of-bounds",
                    Data::Tuple(t),
                    length,
                    index
                ),
                vec![self.current_span()],
            ));
        }

        let data = t[index].clone();
        self.stack.push_data(Data::Tuple(t));
        self.stack.push_data(data);
        self.done()
    }

    /// Call a function on the top of the stack, passing the next value as an
    /// argument.
    fn call(&mut self) -> Result<(), Trace> {
        // get the function and argument to run
        let fun = match self.stack.pop_data() {
            Data::Closure(c) => *c,
            o => {
                return Err(Trace::error(
                    "Call",
                    &format!("The data '{}' is not a function and can not be called", o),
                    vec![self.current_span()],
                ))
            }
        };
        let arg = self.stack.pop_data();

        // TODO: make all programs end in return,
        // so bounds check (i.e. is_terminated) is never required
        self.next();
        let tail_call =
            !self.is_terminated() && Opcode::Return == Opcode::from_byte(self.peek_byte());

        // clear the stack if there's a tail call
        // we must do this before we suspend the calling context
        if tail_call {
            let locals = self.next_number();
            for _ in 0..locals {
                self.del()?;
            }
        }

        // suspend the calling context
        let old_closure = mem::replace(&mut self.closure, fun);
        let old_ip = mem::replace(&mut self.ip, 0);
        let suspend = Suspend {
            ip: old_ip,
            closure: old_closure,
        };

        // if there's a tail call, we don't bother pushing a new frame
        // the topmost frame doesn't carry any context;
        // that context is intrinsic to the Fiber itself.
        if !tail_call {
            self.stack.push_frame(suspend);
        }

        // set up the stack for the function call
        // self.stack.push_frame(suspend);
        self.stack.declare(self.closure.lambda.decls);
        self.stack.push_data(arg);

        // println!("{}", self.closure.lambda);

        Ok(())
    }

    /// Return a value from a function.
    /// End the execution of the current lambda.
    /// Takes the number of locals on the stack
    /// Relpaces the last frame with the value on the top of the stack.
    /// Expects the stack to be a `[..., Frame, Local 1, ..., Local N, Data]`
    fn return_val(&mut self) -> Result<(), Trace> {
        // the value to be returned
        let val = self.stack.pop_data();

        // clear all locals
        let locals = self.next_number();
        for _ in 0..locals {
            self.del()?;
        }

        // restore suspended callee
        let suspend = self.stack.pop_frame(); // remove the frame
        self.ip = suspend.ip;
        self.closure = suspend.closure;

        // push return value
        self.stack.push_data(val); // push the return value
        Ok(())
    }

    fn closure(&mut self) -> Result<(), Trace> {
        let index = self.next_number();

        let lambda = match self.closure.lambda.constants[index].clone() {
            Data::Lambda(lambda) => lambda,
            _ => unreachable!("Expected a lambda to be wrapped with a closure"),
        };

        let mut closure = Closure::wrap(lambda);

        for captured in closure.lambda.captures.iter() {
            let reference = match captured {
                Captured::Local(index) => self.stack.local_ref(*index),
                Captured::Nonlocal(upvalue) => self.closure.captures[*upvalue].clone(),
            };
            closure.captures.push(reference)
        }

        self.stack.push_data(Data::Closure(Box::new(closure)));
        self.done()
    }

    fn ffi_call(&mut self) -> Result<(), Trace> {
        panic!("FFI is depracated");
        // let index = self.next_number();
        // let ffi_function = &self.closure.lambda.ffi[index];
        //
        // let argument = self.stack.pop_data();
        // let returned = match ffi_function.call(argument) {
        //     Ok(d) => d,
        //     Err(e) => return Err(Trace::error(
        //         "FFI Call", &e, vec![self.current_span()],
        //     )),
        // };
        //
        // self.stack.push_data(returned);
        // self.done()
    }
}
